<template>
  <div class="messages gap-col-8" ref="messages" @scroll="onScroll">
    <transition name="expand">
      <Translator
        v-if="translatorOpen || keepTranslate"
        class="translator"
        :user="user"
        :hide-mode="keepTranslatorMore"
      />
    </transition>
    <div v-if="loading" class="tip font-size-12 font-weight-400 line-height-140 text-center">
      {{ $t('common.loading') }}
    </div>
    <MessageWrap
      v-for="msg in user.messages"
      class="message"
      :class="{
        self: msg.self,
        'h-photo': msg.contentType === 'photos' && msg.ext.width >= msg.ext.height,
        'v-photo': msg.contentType === 'photos' && msg.ext.width < msg.ext.height,
      }"
      :key="msg.id"
      @intersect="msg.unread = false"
    >
      <div class="row gap-row-8">
        <Avatar
          v-if="!msg.self"
          class="avatar"
          :src="user.avatar"
          :radius="13"
          :vip="user.isVip"
          :vipTier="user.tier"
        ></Avatar>
        <div class="col gap-col-5">
          <Text
            v-if="
              msg.contentType === 'text' &&
              typeof msg.ext.type === 'undefined' &&
              typeof msg.ext.donateAmount === 'undefined'
            "
            :text="
              user.translate && msg.langTable
                ? typeof msg.langTable[user.translateLang] === 'string'
                  ? msg.langTable[user.translateLang]
                  : msg.langTable.orig
                : msg.content
            "
            :self="msg.self"
            :translating="
              user.translate && msg.langTable && msg.langTable[user.translateLang] === TRANSLATE_STATUS.TRANSLATING
            "
          ></Text>
          <Product
            v-if="msg.contentType === 'text' && msg.ext.type === 1"
            :text="msg.content"
            :item="msg.ext"
            :self="msg.self"
            @click:buy="$chatBuy"
            @click:media="(v) => $emit('click:media', v)"
            @click:creator="(v) => $emit('click:creator', v)"
          ></Product>
          <Subscribe
            v-if="msg.contentType === 'text' && msg.ext.type === 2"
            :text="msg.content"
            :item="msg.ext"
            :self="msg.self"
            @click:subscribe="$chatSubscribe"
            @click:creator="(v) => $emit('click:creator', v)"
          ></Subscribe>
          <Donate
            v-if="msg.contentType === 'text' && typeof msg.ext.donateAmount === 'number'"
            :text="msg.content"
            :self="msg.self"
            :amount="msg.ext.donateAmount"
          ></Donate>
          <Photo v-if="msg.contentType === 'photos'" :url="msg.content" :self="msg.self"></Photo>
          <div class="timestamp font-size-12 font-weight-500 line-height-120 text-input">
            {{
              msg.status === SEND_STATUS.SUCCESS
                ? getTime(msg.timestamp)
                : msg.status === SEND_STATUS.SENDING
                ? $t('message.sending')
                : $t('message.failure')
            }}
          </div>
        </div>
      </div>
    </MessageWrap>
  </div>
  <div v-if="!isLogin" class="divider bg-input"></div>
  <div class="feature gap-row-16" ref="feature">
    <div v-if="isLogin" class="input gap-row-16 bg-surface">
      <button class="uploader">
        <label>
          <Icon size="20" name="attach" class="pointer"></Icon>
          <input
            ref="photoInput"
            type="file"
            accept="image/jpg, image/jpeg, image/png, image/gif"
            @change="sendPhoto"
          />
        </label>
      </button>
      <input
        v-model="textInput"
        class="font-size-14 font-weight-400 line-height-140"
        type="text"
        :placeholder="$t('placeholder.message')"
      />
      <button class="send"><Icon size="20" name="send" @click="sendText"></Icon></button>
    </div>
    <button
      v-if="isLogin"
      class="donate primary-gradient"
      @click="donate({ author: { aff: userId, avatar: user.avatar, nickname: user.nickname } }, 31 /*gtmKey*/)"
    >
      <Icon size="27" name="money"></Icon>
    </button>
    <div v-if="!isLogin" class="font-size-14 font-weight-500 line-height-140 text-input pointer" @click="toLogin">
      <span class="text-primary">{{ $t('label.login') }}</span>
      {{ '/' }}
      <span class="text-primary">{{ $t('label.register') }}</span>
      {{ $t('label.canSendMessage') }}
    </div>
  </div>
  <div v-if="newMsgTip" class="new-msg-tip gap-row-4" @click="onNewMsgTipClick">
    <div class="font-size-14 font-weight-500 line-height-140">{{ $t('label.newMessage') }}</div>
    <Icon size="16" name="arrowDown"></Icon>
  </div>
</template>

<script setup>
import { debounce } from 'lodash-es'
import { computed, onBeforeUpdate, onUpdated, provide, ref, shallowRef, watch } from 'vue'
import { onKeyStroke } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { useAccountStore } from '@/store/account'
import { useAppStore } from '@/store/app'
import { useAuthDialogStore } from '@/store/auth-dialog'
import { useChatStore } from '@/store/chat'
import { useChatPanelStore } from '@/store/chat-panel'
import { useCreatorStore } from '@/store/creator'
import { usePopupDialogStore } from '@/store/popup-dialog'
import { usePopupMessageStore } from '@/store/popup-message'
import { $t } from '@/i18n'
import Avatar from '@/components/multimedia/Avatar.vue'
import { usePreventTouchmove } from '@/compositions/mobile/preventTouchmove'
import { useDialog } from '@/compositions/viewer/dialog'
import { SEND_STATUS, TRANSLATE_STATUS } from '@/constant/chat'
import ChatToSelfError from '@/errors/ChatToSelfError'
import { sendPhotoMessage, sendTextMessage } from '@/ws'
import Translator from './Translator.vue'
import Donate from './message/Donate.vue'
import MessageWrap from './message/MessageWrap.vue'
import Photo from './message/Photo.vue'
import Product from './message/Product.vue'
import Subscribe from './message/Subscribe.vue'
import Text from './message/Text.vue'

const { donate } = useDialog()
const { $alert } = usePopupDialogStore()
const { open: openMessage } = usePopupMessageStore()

const props = defineProps({
  userId: { type: Number },
  keepTranslatorMore: { type: Boolean, default: false },
})
defineEmits(['click:media', 'click:creator'])

const messages = ref(null)
const newMsgTip = ref(false)

const chatStore = useChatStore()
const { ready, translatorOpen, supportTranslate } = storeToRefs(chatStore)
const { getUser, loadNextHistory, setChatCreator, clearChatCreator, $chatSubscribe, $chatBuy } = chatStore
const keepTranslate = computed(() => props.keepTranslatorMore && supportTranslate.value)

const creatorStore = useCreatorStore()
const { getCreator } = creatorStore

const accountStore = useAccountStore()
const { isLogin, userData } = storeToRefs(accountStore)
const { open: openAuth } = useAuthDialogStore()
const { close: closeChatPanel } = useChatPanelStore()

function toLogin() {
  closeChatPanel()
  openAuth()
}

const loading = ref(true)
const user = shallowRef({})

/** 暴露當前 Creator 給任一子層，方便取值 */
provide('creator', user)

watch(
  () => props.userId,
  async (userId, prevUserId, onCleanup) => {
    if (!userId || !isLogin.value || !ready.value) {
      loading.value = true
      user.value = {}
      return
    }

    let cleanup = false
    onCleanup(() => {
      cleanup = true
      clearChatCreator()
    })

    // 載入開始
    loading.value = true
    user.value = {}
    try {
      const newUser = await getUser(userId)
      if (cleanup) return
      user.value = newUser
      setChatCreator(newUser)
      checkNewMessage()
      loadNextHistory(user.value.id, true)
    } catch (e) {
      console.warn(e)
    }
    loading.value = false
  },
  { immediate: true },
)

watch(ready, async (v, prev, onCleanup) => {
  if (!v || !props.userId || !isLogin.value) {
    loading.value = true
    user.value = {}
    return
  }

  let cleanup = false
  onCleanup(() => {
    cleanup = true
    clearChatCreator()
  })

  loading.value = true
  user.value = {}
  try {
    const newUser = await getUser(props.userId)
    if (cleanup) return
    user.value = newUser
    setChatCreator(newUser)
    checkNewMessage()
    loadNextHistory(user.value.id, true)
  } catch (e) {
    console.warn(e)
  }
  loading.value = false
})

const textInput = ref('')
async function sendText() {
  const message = textInput.value.trim()

  if (!message) return

  const uuid = user.value.uuid
  const aff = user.value.id

  const canChat = await checkCanChat()
  if (!canChat) {
    return
  }

  try {
    sendTextMessage(message, uuid, aff)

    textInput.value = ''
    onNewMsgTipClick()
  } catch (e) {
    if (e instanceof ChatToSelfError) {
      $alert({
        content: $t('content.chat.canNotChatToSelf'),
        confirm: () => {},
        showClose: false,
      }).open()
    }
    console.warn(e)
  }
}

const photoInput = ref(null)
async function sendPhoto(evt) {
  const file = evt.target.files[0]
  if (file) {
    const uuid = user.value.uuid
    const aff = user.value.id

    const canChat = await checkCanChat()
    if (!canChat) {
      return
    }

    try {
      sendPhotoMessage(file, uuid, aff)
      photoInput.value.value = null
    } catch (e) {
      if (e instanceof ChatToSelfError) {
        $alert({
          content: $t('content.chat.canNotChatToSelf'),
          confirm: () => {},
          showClose: false,
        }).open()
      }
      console.warn(e)
    }
  }
}

async function checkCanChat() {
  const creator = await getCreator(props.userId)

  if (
    (creator.is_kyc && userData.value.is_kyc && !creator.is_subscribe) || // 雙方都是創作者，且自己沒訂閱對方
    (creator.is_kyc && !userData.value.is_kyc && !creator.is_subscribe) || // 自己不是創作者，對方是創作者，自己沒訂閱對方
    (!creator.is_kyc && !userData.value.is_kyc) // 雙方都不是創作者，來亂的
  ) {
    $alert({
      content: $t('content.chat.subscribeBeforeChat'),
      confirm: () => {},
      showClose: false,
    }).open()

    return false
  }

  return true
}

function getTime(timestamp) {
  const date = new Date(timestamp)
  return (
    String(date.getMonth() + 1).padStart(2, '0') +
    '/' +
    String(date.getDate()).padStart(2, '0') +
    ' ' +
    String(date.getHours()).padStart(2, '0') +
    ':' +
    String(date.getMinutes()).padStart(2, '0')
  )
}

const onScroll = debounce(({ target: { scrollTop, scrollHeight, clientHeight } }) => {
  // 觸頂繼續加載
  if (scrollTop === 0 && !loading.value) {
    if (user.value.noMore) {
      openMessage($t('common.noMore'))
    } else {
      loadNextHistory(user.value.id)
    }
  }

  // 將新訊息提示關掉
  if (scrollHeight - scrollTop - clientHeight < 10) {
    newMsgTip.value = false
  }
}, 200)

// 確保訊息更新後 scroll container 是保持在原來位置不會亂跳
let prevMessagesScrollHeight
let prevMessagesScrollTop
let atBottom = true
onBeforeUpdate(() => {
  prevMessagesScrollHeight = messages.value.scrollHeight
  prevMessagesScrollTop = messages.value.scrollTop

  // 126 是一個約略估計的高度
  atBottom = prevMessagesScrollHeight - prevMessagesScrollTop - messages.value.clientHeight < 126
})
onUpdated(() => {
  const isNewMessage = checkNewMessage()

  // 新訊息改變了 scroll container 高度
  if (isNewMessage) {
    if (atBottom) {
      messages.value.scrollTop = messages.value.scrollHeight
    } else {
      messages.value.scrollTop = prevMessagesScrollTop
      newMsgTip.value = true
    }
  }

  // 歷史訊息改變了 scroll container 高度
  else {
    const scrollHeightDiff = messages.value.scrollHeight - prevMessagesScrollHeight
    const newScrollTop = scrollHeightDiff + prevMessagesScrollTop
    messages.value.scrollTop = newScrollTop
  }
})
function onNewMsgTipClick() {
  messages.value.scrollTop = messages.value.scrollHeight
  newMsgTip.value = false
}

// 判別添加進來的訊息是否是新訊息
let prevLastMessageId
function checkNewMessage() {
  const lastMessageId =
    user.value.messages && user.value.messages.length > 0
      ? user.value.messages[user.value.messages.length - 1].id
      : undefined

  // 這是新訊息
  if (lastMessageId !== prevLastMessageId) {
    prevLastMessageId = lastMessageId
    return true
  }

  return false
}

const feature = ref(null)
usePreventTouchmove(feature)

const appStore = useAppStore()
const { isDesktop } = storeToRefs(appStore)
onKeyStroke('Enter', () => {
  if (isDesktop.value) {
    // 某些中文輸入法按下 enter 時只是確認輸入內容，但整串字尚未被填入欄位中，這邊可以使用按下 enter 時的內容長度與下一禎的內容長度做比較而得知
    const inputLength = textInput.value.length
    requestAnimationFrame(() => {
      if (inputLength === textInput.value.length) {
        sendText()
      }
    })
  }
})
</script>

<style lang="scss" scoped>
.expand-enter-active,
.expand-leave-active {
  transition: transform 0.1s;
  transform-origin: right;
}
.expand-enter-from,
.expand-leave-to {
  transform: scaleX(0);
}

.messages {
  height: calc(100% - 3.5rem); // .feature: 3.5rem
  padding-right: 0.25rem; // 配合 scrollbar
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  position: relative;

  &::-webkit-scrollbar {
    width: 0.25rem;
    display: block;
  }
  &::-webkit-scrollbar-thumb {
    background: var(--color-primary-divider, rgba(215, 215, 215, 0.4));
    border-radius: 0.09375rem;
    border: 0.35rem solid transparent;
  }

  .translator {
    position: sticky;
    top: 0;
    z-index: 1;
  }

  .tip {
    width: fit-content;
    padding: 0.125rem 0.875rem;
    background: var(--color-primary-divider, rgba(215, 215, 215, 0.4));
    border-radius: 1rem;
    align-self: center;
  }

  .message {
    width: 80%;
    margin-left: 0.25rem;

    &.h-photo {
      width: 72%;
    }
    &.v-photo {
      width: 55%;
    }

    &.self {
      margin-left: 0;
      align-self: flex-end;
      display: flex;
      flex-direction: column;
      align-items: flex-end;

      .col {
        align-items: flex-end;
      }
    }

    .row {
      width: 100%;
      display: flex;
      align-items: flex-end;
    }

    .col {
      width: 100%;
      display: flex;
      flex-direction: column;
    }

    .avatar {
      flex-shrink: 0;
      margin-bottom: 1.0625rem;
    }

    // .timestamp {
    //   text-align: right;
    // }
  }
}

.divider {
  width: calc(100% + 2rem);
  height: 1px;
  margin-left: -1rem;
}

.feature {
  height: 3.5rem;
  padding: 0.5rem 0;
  display: flex;
  align-items: center;

  .input {
    flex-grow: 1;
    height: 2.5rem;
    padding: 0 1rem;
    border-radius: 0.75rem;
    display: flex;
    justify-content: center;

    .uploader {
      label {
        width: 1.25rem;
        height: 1.25rem;
        display: block;
      }
      input {
        display: none;
      }
    }

    .send {
      display: flex;
      align-items: center;
    }

    input {
      flex-grow: 1;
      outline: none;
    }
  }

  .donate {
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 2.5rem;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}

.new-msg-tip {
  width: fit-content;
  padding: 0.125rem 0.875rem;
  background: var(--color-primary-divider, rgba(215, 215, 215, 0.4));
  border-radius: 1rem;
  display: flex;
  align-items: center;
  position: absolute;
  bottom: 4rem;
  left: 50%;
  transform: translateX(-50%);
}
</style>
